package info.demo.persistence.hibernate;

import info.demo.entity.EntidadePersistente;
import info.demo.persistence.GenericDAO;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;

import javax.persistence.PersistenceException;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;

public abstract class AbstractHibernateDAO<T extends EntidadePersistente, ID extends Serializable> implements Serializable, GenericDAO<T, ID> {

    private static final long serialVersionUID = -3215871844721788978L;
    
    private SessionFactory sessionFactory;
    private Class<T> persistentClass;

    @SuppressWarnings({"unchecked"})
    public AbstractHibernateDAO() {
        this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public AbstractHibernateDAO(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    protected Session getSession() {
        assert(getSessionFactory() != null) : "SessionFactory has not been set on DAO before usage";
        return getSessionFactory().getCurrentSession();
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }


    @SuppressWarnings("unchecked")
    public T getById(ID id, boolean lock) throws PersistenceException {
        T entity;
        if (lock)
            entity = (T) getSession().load(getPersistentClass(), id, LockMode.UPGRADE);
        else
            entity = (T) getSession().load(getPersistentClass(), id);
        return entity;
    }

    public T getById(ID id) throws PersistenceException {
        return getById(id, false);
    }

    @SuppressWarnings("unchecked")
    public T findById(ID id, boolean lock) throws PersistenceException {
        T entity;
        if (lock)
            entity = (T) getSession().get(getPersistentClass(), id, LockMode.UPGRADE);
        else
            entity = (T) getSession().get(getPersistentClass(), id);
        if (entity != null)
            getSession().refresh(entity); // Atualiza o objeto para casos em que se reutiliza a Session para mais de uma a&ccedil;&atilde;o
        return entity;
    }

    public T findById(ID id) throws PersistenceException {
        return findById(id, false);
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() throws PersistenceException {
        return findByCriteria();
    }
    
    @SuppressWarnings("unchecked")
    public T saveOrUpdate(T entity) throws PersistenceException {
        getSession().saveOrUpdate(entity);
        getSession().flush();
        return entity;
    }

    public T save(T entity) throws PersistenceException {
        getSession().save(entity);
        getSession().flush();
        return entity;
    }

    public T update(T entity) throws PersistenceException {
        getSession().update(entity);
        getSession().flush();
        return entity;
    }

    public T delete(T entity) throws PersistenceException {
        getSession().evict(entity);
    	getSession().delete(entity);
        getSession().flush();
        return entity;
    }

    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(Criterion... criterion) {
        return getCriteria(criterion).list();
    }

    protected Criteria getCriteria(Criterion... criterion) {
        Criteria criteria = getSession().createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            criteria.add(c);
        }
        return criteria;
    }

    protected void setParameters(Query query, Map<String, Object> parameters) {
        if (query != null && parameters != null) {
            for (Map.Entry<String, Object> entry : parameters.entrySet())
                query.setParameter(entry.getKey(), entry.getValue());
        }
    }

    protected int calculateQueryRows(String hql, Map<String, Object> parameters) {
        String lowerHql = hql.trim().toLowerCase();
        StringBuilder countHql = new StringBuilder();
        if (lowerHql.matches("^select[ ]*distinct.*")) { // Para sql com distinct, o count precisa ser diferente
            Query query = getSession().createQuery(hql);
            setParameters(query, parameters);
            ScrollableResults sr = query.scroll();
            sr.last();
            return sr.getRowNumber() + 1; // RowNumber inicia em zero
        } else {
            countHql.append("select count(*) ").append(hql.substring(lowerHql.indexOf("from")));
            Query query = getSession().createQuery(StringUtils.remove(countHql.toString(),"fetch"));
            setParameters(query, parameters);
            return ((Number) query.iterate().next()).intValue();
        }
    }

}
